package com.dynamic.mybatis.autoconfigure;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.dynamic.mybatis.core.datasource.DynamicMultipleDataSource;
import com.dynamic.mybatis.core.intercept.DynamicDataSourceHandler;
import com.dynamic.mybatis.core.intercept.DynamicMappedStatementHandler;
import com.dynamic.mybatis.core.intercept.DynamicSqlSessionExecuteHandler;
import com.dynamic.mybatis.core.metadata.DynamicConfiguration;
import com.dynamic.mybatis.core.metadata.DynamicMappedStatement;
import com.dynamic.mybatis.core.parser.NamespaceHelper;
import com.dynamic.mybatis.core.serialization.DataSourcePersistenceHandler;
import com.dynamic.mybatis.core.serialization.MappedStatementInitializeHandler;
import com.dynamic.mybatis.core.serialization.MappedStatementPersistenceHandler;
import com.dynamic.mybatis.core.serialization.mapper.DataSourceMapper;
import com.dynamic.mybatis.core.serialization.mapper.MappedStatementMapper;
import com.dynamic.mybatis.core.session.DynamicSqlSessionDataSource;
import com.dynamic.mybatis.core.session.DynamicSqlSessionTemplate;
import com.dynamic.mybatis.web.mvc.MappedStatementRequestMappingRegister;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.Environment;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Configuration
@AutoConfigureAfter(DynamicSqlSessionTemplate.class)
@MapperScan("com.dynamic.mybatis.core.serialization.mapper")
public class DynamicMybatisInitializeAutoConfig implements ApplicationContextAware , ApplicationRunner {
    private static final Log log = LogFactory.getLog(DynamicMybatisInitializeAutoConfig.class);

    private ApplicationContext applicationContext;
    @Autowired
    private MappedStatementRequestMappingRegister mappedStatementRequestMappingRegister;
    @Autowired
    private DynamicSqlSessionTemplate dynamicSqlSessionTemplate;
    private MappedStatementInitializeHandler initializeHandler;

    @Value("${dynamic-mapped-statement.data-source.key:}")
    private String dynamicMappedStatementDataSourceKey;

    @Bean
    public DataSourcePersistenceHandler dataSourcePersistenceHandler(DataSourceMapper dataSourceMapper){
        return new DataSourcePersistenceHandler(dataSourceMapper);
    }

    @Bean
    public MappedStatementPersistenceHandler mappedStatementPersistenceHandler(MappedStatementMapper mappedStatementMapper){
        return new MappedStatementPersistenceHandler(mappedStatementMapper);
    }

    @Bean
    public MappedStatementInitializeHandler mappedStatementInitializeHandler(){
        if(StringUtils.isBlank(dynamicMappedStatementDataSourceKey)){
            log.warn("environment {dynamic-mapped-statement.data-source.key} is empty.");
        }
        return initializeHandler = new MappedStatementInitializeHandler(dynamicMappedStatementDataSourceKey);
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        this.addApplicationContextDataSource(applicationContext,dynamicSqlSessionTemplate);
        this.addDataBaseDataSource(applicationContext,dynamicSqlSessionTemplate,initializeHandler);
        this.addDataBaseMappedStatement(applicationContext,dynamicSqlSessionTemplate,initializeHandler);

        this.dynamicDataSourceHandler(applicationContext,dynamicSqlSessionTemplate);
        this.dynamicSqlSessionExecuteHandler(applicationContext,dynamicSqlSessionTemplate);
        this.addDynamicMappedStatementHandler(applicationContext,dynamicSqlSessionTemplate);
        this.addSpringWebMvcMapping(applicationContext,dynamicSqlSessionTemplate);
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }


    /**
     * 初始化添加sql执行拦截
     * @param applicationContext
     * @param dynamicSqlSessionTemplate
     */
    public void dynamicSqlSessionExecuteHandler(ApplicationContext applicationContext, DynamicSqlSessionTemplate dynamicSqlSessionTemplate) {
        log.debug("dynamicSqlSessionExecuteHandler");
        Map<String, DynamicSqlSessionExecuteHandler> handlers = applicationContext.getBeansOfType(DynamicSqlSessionExecuteHandler.class);
        if (handlers != null && handlers.size() > 0) {
            dynamicSqlSessionTemplate.addSqlSessionHandler(handlers.values());
        }
    }

    /**
     * 初始化添加数据源执行拦截
     * @param applicationContext
     * @param dynamicSqlSessionTemplate
     */
    public void dynamicDataSourceHandler(ApplicationContext applicationContext, DynamicSqlSessionTemplate dynamicSqlSessionTemplate) {
        log.debug("dynamicDataSourceHandler");
        Map<String, DynamicDataSourceHandler> handlers = applicationContext.getBeansOfType(DynamicDataSourceHandler.class);
        if (handlers != null && handlers.size() > 0) {
            dynamicSqlSessionTemplate.addDataSourceHandler(handlers.values());
        }
    }

    /**
     * 初始化添加DynamicMappedStatement执行拦截
     * @param applicationContext
     * @param dynamicSqlSessionTemplate
     */
    public void addDynamicMappedStatementHandler(ApplicationContext applicationContext, DynamicSqlSessionTemplate dynamicSqlSessionTemplate) {
        log.debug("addDynamicMappedStatementHandler");
        org.apache.ibatis.session.Configuration configuration = dynamicSqlSessionTemplate.getSqlSession().getConfiguration();
        if(!(configuration instanceof DynamicConfiguration)){
            return;
        }
        DynamicConfiguration dynamicConfiguration = (DynamicConfiguration) configuration;
        Map<String, DynamicMappedStatementHandler> handlers = applicationContext.getBeansOfType(DynamicMappedStatementHandler.class);
        if (handlers != null && handlers.size() > 0) {
            dynamicConfiguration.addMappedStatementHandler(handlers.values());
        }
    }


    /**
     * 将数据库配置的数据源添加到动态数据源中
     * @param applicationContext
     * @param dynamicSqlSessionTemplate
     */
    public void addDataBaseDataSource(ApplicationContext applicationContext, DynamicSqlSessionTemplate dynamicSqlSessionTemplate,MappedStatementInitializeHandler initializeHandler) {
        log.debug("addDataBaseDataSource");
        initializeHandler.initDatasource(dynamicSqlSessionTemplate,applicationContext.getBean(DataSourceMapper.class));
    }

    /**
     * 将数据库配置的数据源添加到动态数据源中
     * @param applicationContext
     * @param dynamicSqlSessionTemplate
     */
    public void addDataBaseMappedStatement(ApplicationContext applicationContext, DynamicSqlSessionTemplate dynamicSqlSessionTemplate,MappedStatementInitializeHandler initializeHandler) {
        log.debug("addDataBaseMappedStatement");
        initializeHandler.initMappedStatement(dynamicSqlSessionTemplate,applicationContext.getBean(MappedStatementMapper.class));
    }

    public void addSpringWebMvcMapping(ApplicationContext applicationContext, DynamicSqlSessionTemplate dynamicSqlSessionTemplate) {
        log.debug("addSpringWebMvcMapping");
         org.apache.ibatis.session.Configuration configuration = dynamicSqlSessionTemplate.getSqlSession().getConfiguration();
         if(!(configuration instanceof DynamicConfiguration)){
            return;
         }
        List<DynamicMappedStatement> dynamicMappedStatements = ((DynamicConfiguration) configuration).getDynamicMappedStatements().values().stream().collect(Collectors.toList());
        mappedStatementRequestMappingRegister.registerMapping(DynamicMappedStatementHandler.OPERATE.ADD, dynamicMappedStatements);
    }



    /**
     * 将上下文数据源添加到动态数据源中
     * @param applicationContext
     * @param dynamicSqlSessionTemplate
     */
    public void addApplicationContextDataSource(ApplicationContext applicationContext, DynamicSqlSessionTemplate dynamicSqlSessionTemplate) {
        log.debug("addApplicationContextDataSource");
        DynamicMultipleDataSource dynamicMultipleDataSource = null;
        DataSource envDataSource = dynamicSqlSessionTemplate.getDataSource();
        if(envDataSource != null && envDataSource instanceof DynamicMultipleDataSource){
            dynamicMultipleDataSource = (DynamicMultipleDataSource) envDataSource;
        }
        //初始化动态数据源
        Map<String, DataSource> dataSources = applicationContext.getBeansOfType(DataSource.class);
        DataSource dataSource;
        try {
            dataSource = applicationContext.getBean(DataSource.class);
        } catch (Exception e) {
            dataSource = dataSources.values().stream().filter(d -> !(d instanceof DynamicMultipleDataSource)).findFirst().orElse(null);
        }
        DynamicMultipleDataSource    springDynamicMultipleDataSource = dataSources != null ?
                (DynamicMultipleDataSource) dataSources.values().stream()
                .filter(d -> d instanceof DynamicMultipleDataSource).findFirst().orElse(null)
                : null;
        if(dynamicMultipleDataSource == null){
            dynamicMultipleDataSource = springDynamicMultipleDataSource != null ? springDynamicMultipleDataSource : new DynamicMultipleDataSource();
        }
        //spirng中dynamicMultipleDataSource 设置到  mybatis Environment datasource 中
        if (springDynamicMultipleDataSource != null && dynamicMultipleDataSource != springDynamicMultipleDataSource) {
            springDynamicMultipleDataSource.putAll(dynamicMultipleDataSource.getDataSources());
            dynamicMultipleDataSource = springDynamicMultipleDataSource;
            Environment env =  dynamicSqlSessionTemplate.getSqlSession().getConfiguration().getEnvironment();
            Environment newEnv = new  Environment.Builder(env.getId()).dataSource(dynamicMultipleDataSource).transactionFactory(env.getTransactionFactory()).build();
            dynamicSqlSessionTemplate.getSqlSession().getConfiguration().setEnvironment(newEnv);
        }

        Map<Object, DataSource> dataSourceHashMap = new HashMap<>();
        if (CollectionUtils.isNotEmpty(dataSources)) {
            for (Map.Entry<String, DataSource> entry : dataSources.entrySet()) {
                if (entry.getValue() instanceof DynamicMultipleDataSource) {
                    continue;
                }
                dataSourceHashMap.put(NamespaceHelper.getIdentity(NamespaceHelper.DEFAULT_NAMESPACE, entry.getKey()), entry.getValue());
            }
        }
        dynamicMultipleDataSource.set(dataSource, dataSourceHashMap);

        DynamicSqlSessionDataSource dynamicSqlSessionDataSource = applicationContext.getBean(DynamicSqlSessionDataSource.class);
        //数据源属性初始化
        if (dynamicMultipleDataSource != null) {
            dynamicSqlSessionDataSource.afterDataSourcePropertiesSet();
        }
    }
}
